package com.yahoo.labs.snow;

import java.lang.reflect.InvocationTargetException;

import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPException;
import com.martiansoftware.jsap.JSAPResult;
import com.martiansoftware.jsap.Parameter;
import com.martiansoftware.jsap.SimpleJSAP;
import com.martiansoftware.jsap.Switch;
import com.yahoo.labs.snow.solver.MultiplicativeEffortProduceAndChooseSolver;
import com.yahoo.labs.snow.solver.ProduceAndChooseSolver;
import com.yahoo.labs.snow.solver.ProduceAndChooseSolver.RankingStrategy;
import com.yahoo.labs.snow.solver.RandomSOBOSolver;
import com.yahoo.labs.snow.solver.SequentialScanSolver;
import com.yahoo.labs.snow.solver.Solver;
import com.yahoo.labs.snow.util.Id2Str;

/**
 * Main class of the Snowflakes package.
 * 
 * Implements composite-retrieval algorithms as described in forthcoming paper by Sihem Amer-Yahia,
 * Francesco Bonchi, Carlos Castillo, Esteban Feuerstein and Aristides Gionis.
 * 
 * @author chato
 * 
 */
public class Snowflakes {

	final static int DEFAULT_NUM_SNOWFLAKES = 5;

	public static void main(String[] args) throws JSAPException, ClassNotFoundException, IllegalArgumentException, SecurityException, InstantiationException,
			IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		final SimpleJSAP jsap = new SimpleJSAP(Snowflakes.class.getName(), "Creates snowflakes given a proble instance.", new Parameter[] {
				new FlaggedOption("input-directory", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, 'd', "input-directory",
						"Input directory of the problem instance, containing 3 files: " + ProblemInstanceFromDirectory.FILENAME_COST + ", "
								+ ProblemInstanceFromDirectory.FILENAME_COMPAT + ", " + ProblemInstanceFromDirectory.FILENAME_COVER),
				new FlaggedOption("input-cost", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "input-cost",
						"Input file containing costs, pairs (id,cost)."),
				new FlaggedOption("input-compat", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "input-compat",
						"Input file containing compatibility, triples (id1,id2,compat)."),
				new FlaggedOption("input-cover", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "input-cover",
						"Input file containing covered attributes, tuples (id,cover1,cover2,...,coverK)."),
				new FlaggedOption("budget", JSAP.LONG_PARSER, JSAP.NO_DEFAULT, JSAP.REQUIRED, 'b', "budget", "Maximum budget available."),
				new FlaggedOption("number-snowflakes", JSAP.INTEGER_PARSER, Integer.toString(DEFAULT_NUM_SNOWFLAKES), JSAP.NOT_REQUIRED, 'n', "number-snowflakes",
						"The number of snowflakes to return"),
				new FlaggedOption("solver", JSAP.STRING_PARSER, SequentialScanSolver.class.getSimpleName(), JSAP.REQUIRED, 's', "solver", "The algorithm to use"),
				new FlaggedOption("node2name", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "node2name",
						"A mapping from node ids to names. Tab-separated: node-id<tab>node-name<newline>"),
				new FlaggedOption("type2name", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.REQUIRED, JSAP.NO_SHORTFLAG, "type2name",
						"A mapping from type ids to names. Tab-separated: type-id<tab>type-name<newline>"),
				new FlaggedOption("ranking-strategy", JSAP.STRING_PARSER, ProduceAndChooseSolver.RankingStrategy.getDefault().name(), JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "ranking-strategy",
						"When using a " + ProduceAndChooseSolver.class.getSimpleName() + ", ranking strategy to use: " + ProduceAndChooseSolver.RankingStrategy.RANK_BY_INTRA.name() + " or " + ProduceAndChooseSolver.RankingStrategy.RANK_BY_INTRA_INTER.name() ),
				new FlaggedOption("inter-similarity-weight", JSAP.DOUBLE_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "inter-similarity-weight",
						"When using ranking strategy " + ProduceAndChooseSolver.RankingStrategy.RANK_BY_INTRA_INTER.name() + ", weight of inter-similarity (alpha). The weight of intra-similarity will be (1-alpha)"),
				new FlaggedOption("num-candidates-multiplier", JSAP.DOUBLE_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, JSAP.NO_SHORTFLAG, "num-candidates-multiplier",
						"When using a " + RandomSOBOSolver.class.getSimpleName() + ", the multiplier of the number of requested snowflakes that must be generated (e.g. 2.0 => generate twice as many snowflakes as requested, then keep the top 1/2)" ),
				new Switch("normalize-compat", JSAP.NO_SHORTFLAG, "normalize-compat", "Normalize the compatibility values before running the methods (dividing by larger compatibility)" ),
		});

		final JSAPResult params = jsap.parse(args);
		if (jsap.messagePrinted()) {
			return;
		}

		long budget = params.getLong("budget");

		// Load problem instance
		ProblemInstance problem;
		if (params.userSpecified("input-directory")) {
			problem = new ProblemInstanceFromDirectory(params.getString("input-directory"), budget);
		} else if (params.userSpecified("input-cost") && params.userSpecified("input-compat") && params.userSpecified("input-cover")) {
			problem = new ProblemInstanceFromFiles(params.getString("input-cost"), params.getString("input-compat"), params.getString("input-cover"), budget);
		} else {
			throw new IllegalArgumentException("Must specify either an input directory, or a set of input files");
		}
		
		// Normalize if necessary
		if (params.getBoolean("normalize-compat") ) {
			problem.normalizeNodeCompat();
		}
		
		// Instanciate solver
		String solverName = params.getString("solver");
		Class<?> solverClass = Class.forName(Solver.class.getPackage().getName() + "." + solverName);
		Solver solver = (Solver) solverClass.getConstructor(new Class[] { ProblemInstance.class }).newInstance(new Object[] { problem });
		int numSnowflakes = params.getInt("number-snowflakes");
		
		// Configure solver
		if(params.userSpecified("ranking-strategy") ) {
			((ProduceAndChooseSolver)solver).setRankingStrategy(ProduceAndChooseSolver.RankingStrategy.valueOf(params.getString("ranking-strategy")));
		}
		if(params.userSpecified("inter-similarity-weight") ) {
			RankingStrategy rankingStrategy = ((ProduceAndChooseSolver)solver).getRankingStrategy();
			if( rankingStrategy == RankingStrategy.RANK_BY_INTRA_INTER || rankingStrategy == RankingStrategy.RANK_BY_DENSEST_SUBGRAPH) {
				((ProduceAndChooseSolver)solver).setInterSimilarityWeight(params.getDouble("inter-similarity-weight") );
			} else {
				throw new IllegalArgumentException("This requires --ranking-strategy " + RankingStrategy.RANK_BY_INTRA_INTER.name() + " or " +  RankingStrategy.RANK_BY_DENSEST_SUBGRAPH.name() );
			}
		}
		if(params.userSpecified("num-candidates-multiplier") ) {
			((MultiplicativeEffortProduceAndChooseSolver)solver).setNumCandidatesMultiplier(params.getDouble("num-candidates-multiplier") );
		}

		// Get bundles in the solution
		Snowflake[] solution = solver.solve(numSnowflakes).toArray(new Snowflake[] {});

		// Sort by decreasing sum of compatibilities
		Snowflake.sortByDecreasingSumCompat(solution);

		Id2Str node2name = null;
		if (params.userSpecified("node2name")) {
			node2name = new Id2Str(params.getString("node2name"));
		}

		int avgCost = 0;
		int avgCover = 0;
		int avgSize = 0;
		int avgSumIntraCompat = 0;
		for (int i = 0; i < solution.length; i++) {
			System.out.println("Snowflake #" + i);
			Snowflake snowflake = solution[i];
			System.out.println(snowflake.toString(node2name));
			System.out.println();

			avgCost += snowflake.getCost();
			avgCover += snowflake.getCoverSize();
			avgSize += snowflake.elements.size();
			avgSumIntraCompat += snowflake.getSumIntraCompat();
		}

		System.out.println("SOLUTION_SIZE = " + solution.length);
		System.out.println("AVG_COST = " + avgCost / (double) solution.length);
		System.out.println("AVG_COVER = " + avgCover / (double) solution.length);
		System.out.println("AVG_SIZE = " + avgSize / (double) solution.length);
		System.out.println("AVG_SUM_INTRA_COMPAT = " + avgSumIntraCompat / (double) solution.length);

		double sumMaxPairwiseCompatibilities = 0.0;
		for (int i = 0; i < solution.length; i++) {
			for (int j = i + 1; j < solution.length; j++) {
				sumMaxPairwiseCompatibilities += problem.maxPairwiseCompatibility(solution[i], solution[j]);
			}
		}
		System.out.println("SUM_INTER_COMPATS = " + sumMaxPairwiseCompatibilities);

		System.out.println("# SUM_INTER_COMPATS");
		System.out.println("# Sum of (max(compat(n1,n2) n1 in S1, n2 in S2)) for all pairs (S1,S2) of snowflakes");
	}
}
